/******************************************************************/
/*                                                                */
/*  Winpooch : Windows Watchdog                                   */
/*  Copyright (C) 2004-2007  Benoit Blanchon                      */
/*                                                                */
/*  This program is free software; you can redistribute it        */
/*  and/or modify it under the terms of the GNU General Public    */
/*  License as published by the Free Software Foundation; either  */
/*  version 2 of the License, or (at your option) any later       */
/*  version.                                                      */
/*                                                                */
/*  This program is distributed in the hope that it will be       */
/*  useful, but WITHOUT ANY WARRANTY; without even the implied    */
/*  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR       */
/*  PURPOSE.  See the GNU General Public License for more         */
/*  details.                                                      */
/*                                                                */
/*  You should have received a copy of the GNU General Public     */
/*  License along with this program; if not, write to the Free    */
/*  Software Foundation, Inc.,                                    */
/*  675 Mass Ave, Cambridge, MA 02139, USA.                       */
/*                                                                */
/******************************************************************/


/******************************************************************/
/* Build configuration                                            */
/******************************************************************/

#define	TRACE_LEVEL	2


/******************************************************************/
/* Includes                                                       */
/******************************************************************/

// module's interface
#include "ProcInfoWnd.h"

// standard headers
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <tchar.h>

// project's headers
#include "Assert.h"
#include "Language.h"
#include "ProcList.h"
#include "ProjectInfo.h"
#include "Resources.h"
#include "SpyServer.h"
#include "Trace.h"


/******************************************************************/
/* Internal constants                                             */
/******************************************************************/

// window class name
#define WC_PROCINFOWND TEXT("ProcInfoWnd")

#define WM_SET_PROC_ADDRESS	(WM_USER+11)
#define WM_REFRESH_GENERAL_INFO (WM_USER+12)
#define WM_REFRESH_THREADS_INFO (WM_USER+13)
#define WM_REFRESH_OBJECTS_INFO	(WM_USER+14)
#define WM_REFRESH		(WM_USER+15)

#define TAB_GENERAL		0
#define TAB_THREADS		1
#define TAB_OBJECTS		2

#define GEN_CURRENT_DIRECTORY	0
#define GEN_DLL_PATH		1
#define GEN_IMAGE_PATH_NAME	2
#define GEN_COMMAND_LINE	3
#define GEN_WINDOW_TITLE	4
#define GEN_DESKTOP		5
#define GEN_PROCESS_ID		6
#define GEN_PROCESS_ADDRESS	7

#define THR_THREAD_ID		0
#define THR_KERNEL_TIME		1
#define THR_USER_TIME		2

#define OBJ_TYPE		0
#define OBJ_NAME		1

LPCTSTR g_szGeneral = TEXT("General") ;
LPCTSTR g_szThreads = TEXT("Threads") ;
LPCTSTR g_szObjects = TEXT("Objects") ;
LPCTSTR g_szEnvironment = TEXT("Environment") ;

LPCTSTR g_szCurrentDirectory = TEXT("Current directory") ;
LPCTSTR g_szDllPath = TEXT("DLL path") ;
LPCTSTR g_szImagePathName = TEXT("Image path name") ;
LPCTSTR g_szCommandLine = TEXT("Command line") ;
LPCTSTR g_szWindowTitle = TEXT("Window title") ;
LPCTSTR g_szDesktop = TEXT("Desktop") ;
LPCTSTR g_szProcessId = TEXT("Process ID") ;
LPCTSTR g_szProcessAddress = TEXT("Process address") ;

LPCTSTR g_szThreadId = TEXT("Thread ID") ;
LPCTSTR g_szKernelTime = TEXT("Kernel time") ;
LPCTSTR g_szUserTime = TEXT("User time") ;

LPCTSTR g_szType = TEXT("Type") ;
LPCTSTR g_szName = TEXT("Name") ;

/******************************************************************/
/* Internal data types                                            */
/******************************************************************/

typedef struct {
  HINSTANCE	hInstance ;
  HWND		hwndTabControl ; 
  HWND		hwndListView ;	
  PROCADDR	nProcAddress ;
} WNDDATA ;


/******************************************************************/
/* Internal functions                                             */
/******************************************************************/

LRESULT CALLBACK _ProcInfoWnd_WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) ;


/******************************************************************/
/* Exported function : RegisterClass                              */
/******************************************************************/

BOOL ProcInfoWnd_RegisterClass (HINSTANCE hInstance) 
{
  WNDCLASS wndclass ;

  wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  wndclass.lpfnWndProc   = _ProcInfoWnd_WndProc ;
  wndclass.cbClsExtra    = 0 ;
  wndclass.cbWndExtra    = 0 ;
  wndclass.hInstance     = hInstance ;
  wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
  wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  wndclass.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1) ;
  wndclass.lpszMenuName  = NULL ;
  wndclass.lpszClassName = WC_PROCINFOWND ;

  return 0!=RegisterClass (&wndclass) ;
}


/******************************************************************/
/* Exported function : CreateWindow                               */
/******************************************************************/

HWND ProcInfoWnd_CreateWindow (HINSTANCE hInstance, HWND hwndParent)
{  
  return CreateWindow (WC_PROCINFOWND, NULL,
		       WS_CHILD|WS_VISIBLE,
		       CW_USEDEFAULT, CW_USEDEFAULT,
		       CW_USEDEFAULT, CW_USEDEFAULT, 
		       hwndParent, NULL, hInstance, NULL) ;
}


VOID ProcInfoWnd_SetProcAddress (HWND hwnd, PROCADDR nProcAddress) 
{
  SendMessage (hwnd, WM_SET_PROC_ADDRESS, 0, nProcAddress) ;
}


VOID _ProcInfoWnd_AddFieldName (HWND hwndListView, INT nFieldIndex, LPCTSTR szFieldName) 
{
  LVITEM lvi ;
  
  lvi.mask = LVIF_TEXT ;
  lvi.iSubItem = 0 ;  
  lvi.iItem = nFieldIndex ;
  lvi.pszText = (LPTSTR)szFieldName ;

  ListView_InsertItem (hwndListView, &lvi) ;
}

VOID _ProcInfoWnd_SetFieldValue (HWND hwndListView, INT nFieldIndex, LPCTSTR szFieldValue) 
{
  LVITEM lvi ;
  
  lvi.mask = LVIF_TEXT ;
  lvi.iSubItem = 1 ;  
  lvi.iItem = nFieldIndex ;
  lvi.pszText = (LPTSTR)szFieldValue ;

  ListView_SetItem (hwndListView, &lvi) ;
}


/******************************************************************/
/* Internal function : WndProc                                    */
/******************************************************************/

LRESULT CALLBACK _ProcInfoWnd_WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  WNDDATA * pData = (WNDDATA*) GetWindowLong (hwnd, GWL_USERDATA) ;

  TCITEM	tci ;
  int		nWidth ;
  int		nHeight ;
  RECT		rect ;

  switch (message)
    {
    case WM_CREATE:
      {
	pData = calloc (1, sizeof(WNDDATA)) ;
	SetWindowLong (hwnd, GWL_USERDATA, (LONG)pData) ;

	pData->hInstance = ((CREATESTRUCT*)lParam)->hInstance ;
	
	pData->hwndTabControl = CreateWindow   (WC_TABCONTROL, NULL, WS_CHILD|WS_VISIBLE,
						0,0,0,0, hwnd, (HMENU)IDC_PROCINFOTAB, pData->hInstance, NULL) ;
	if( pData->hwndTabControl==NULL ) TRACE_ERROR (TEXT("Failed to create tab control (error=%u)\n"), GetLastError()) ;

	pData->hwndListView = CreateWindowEx (WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL,
					      WS_CHILD|WS_VISIBLE|WS_VSCROLL|LVS_REPORT|LVS_SINGLESEL,
					      0,0,0,0, hwnd, NULL, pData->hInstance, NULL) ;
	if( pData->hwndListView==NULL ) TRACE_ERROR (TEXT("Failed to create list view control (error=%u)\n"), GetLastError()) ;
	
	SendMessage (pData->hwndTabControl, WM_SETFONT, 
		     (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0) ;

	tci.mask = TCIF_TEXT ;
	tci.pszText = (LPTSTR)g_szGeneral ;
	TabCtrl_InsertItem (pData->hwndTabControl, TAB_GENERAL, &tci) ;

	tci.mask = TCIF_TEXT ;
	tci.pszText = (LPTSTR)g_szThreads ;
	TabCtrl_InsertItem (pData->hwndTabControl, TAB_THREADS, &tci) ;

	tci.mask = TCIF_TEXT ;
	tci.pszText = (LPTSTR)g_szObjects ;
	TabCtrl_InsertItem (pData->hwndTabControl, TAB_OBJECTS, &tci) ;

	SendMessage (hwnd, WM_LANGUAGECHANGED, 0, 0) ;
      }
	
      return 0 ; // case WM_CREATE:

    case WM_DESTROY:

      free (pData) ;
      SetWindowLong (hwnd, GWL_USERDATA, 0) ;

      return 0 ;

    case WM_LANGUAGECHANGED:

      return 0 ;

    case WM_SIZE:

      nWidth = LOWORD (lParam) ;
      nHeight = HIWORD (lParam) ;

      MoveWindow (pData->hwndTabControl, 0, 0, nWidth, nHeight, TRUE) ;

      rect.left = 0 ;
      rect.top = 0 ;
      rect.right = nWidth ;
      rect.bottom = nHeight ;
      
      TabCtrl_AdjustRect (pData->hwndTabControl, FALSE, &rect) ;

      MoveWindow (pData->hwndListView, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top, TRUE) ;

      return 0 ;

    case WM_SET_PROC_ADDRESS:
      
      pData->nProcAddress = (PROCADDR) lParam ;

      PostMessage (hwnd, WM_REFRESH, 0, 0) ;

      return 0 ;

    case WM_NOTIFY:
      
      switch( ((NMHDR*)lParam)->code )
	{
	case TCN_SELCHANGE:
	  PostMessage (hwnd, WM_REFRESH, 0, 0) ;	 
	  break ;
	}
      
      return 0 ;

    case WM_REFRESH:

      switch(  TabCtrl_GetCurSel (pData->hwndTabControl) )
	{
	case TAB_GENERAL:
	  return SendMessage (hwnd, WM_REFRESH_GENERAL_INFO, 0, 0) ;

	case TAB_THREADS:
	  return SendMessage (hwnd, WM_REFRESH_THREADS_INFO, 0, 0) ;

	case TAB_OBJECTS:
	  return SendMessage (hwnd, WM_REFRESH_OBJECTS_INFO, 0, 0) ;
	
	default:
	  return 0 ;
	}

      break ;

    case WM_REFRESH_GENERAL_INFO:
      {
	LVCOLUMN lvc ;

	PROCINFOGENERAL info ;
	TCHAR szProcessId[16], szProcessAddress[16] ;	
	
	ListView_DeleteColumn (pData->hwndListView, 1) ;
	ListView_DeleteColumn (pData->hwndListView, 2) ;
	ListView_DeleteColumn (pData->hwndListView, 3) ;
	ListView_DeleteColumn (pData->hwndListView, 4) ;
	ListView_DeleteAllItems (pData->hwndListView) ;

	if( pData->nProcAddress == 0 ) return 0 ;

	if( ! SpySrv_GetProcInfoGeneral (pData->nProcAddress, &info) )
	  return 0 ;

	wsprintf (szProcessId, TEXT("%u"), info.nProcessId) ;
	wsprintf (szProcessAddress, TEXT("0x%08X"), info.nProcessAddress) ;

	lvc.mask = LVCF_WIDTH ;
	lvc.cx = 100 ;
	lvc.iSubItem = 0 ;
	ListView_InsertColumn (pData->hwndListView, 0, &lvc) ;

	lvc.mask = LVCF_WIDTH ;
	lvc.cx = 500 ;
	lvc.iSubItem = 1 ;
	ListView_InsertColumn (pData->hwndListView, 1, &lvc) ;

	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_CURRENT_DIRECTORY, g_szCurrentDirectory) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_DLL_PATH, g_szDllPath) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_IMAGE_PATH_NAME, g_szImagePathName) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_COMMAND_LINE, g_szCommandLine) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_WINDOW_TITLE, g_szWindowTitle) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_DESKTOP, g_szDesktop) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_PROCESS_ID, g_szProcessId) ;
	_ProcInfoWnd_AddFieldName (pData->hwndListView, GEN_PROCESS_ADDRESS, g_szProcessAddress) ;

	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_CURRENT_DIRECTORY, info.szCurrentDirectory) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_DLL_PATH, info.szDllPath) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_IMAGE_PATH_NAME, info.szImagePathName) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_COMMAND_LINE, info.szCommandLine) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_WINDOW_TITLE, info.szWindowTitle) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_DESKTOP, info.szDesktop) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_PROCESS_ID, szProcessId) ;
	_ProcInfoWnd_SetFieldValue (pData->hwndListView, GEN_PROCESS_ADDRESS, szProcessAddress) ;
      }
      return 0 ;

    case WM_REFRESH_THREADS_INFO:
      {
	PROCINFOTHREADS *pInfo ;
	LVCOLUMN lvc ;

	int i ;
	
	ListView_DeleteAllItems (pData->hwndListView) ;

	if( pData->nProcAddress == 0 ) return 0 ;

	pInfo = (PROCINFOTHREADS*) malloc(1024) ;

	if( ! SpySrv_GetProcInfoThreads (pData->nProcAddress, pInfo, 1024) )
	  {
	    TRACE_ERROR (TEXT("SpySrv_GetProcInfoThreads failed\n")) ;
	    free (pInfo) ;
	    return 0 ;
	  }
	
	lvc.mask = LVCF_WIDTH|LVCF_TEXT ;
	lvc.cx = 50 ;
	lvc.iSubItem = 0 ;
	lvc.pszText = (LPTSTR)g_szThreadId ;
	ListView_InsertColumn (pData->hwndListView, 0, &lvc) ;

	lvc.mask = LVCF_WIDTH|LVCF_TEXT ;
	lvc.cx = 100 ;
	lvc.iSubItem = 1 ;
	lvc.pszText = (LPTSTR)g_szKernelTime ;
	ListView_InsertColumn (pData->hwndListView, 1, &lvc) ;

	lvc.mask = LVCF_WIDTH|LVCF_TEXT ;
	lvc.cx = 100 ;
	lvc.iSubItem = 2 ;
	lvc.pszText = (LPTSTR)g_szUserTime ;
	ListView_InsertColumn (pData->hwndListView, 2, &lvc) ;

	ListView_DeleteColumn (pData->hwndListView, 3) ;
	ListView_DeleteColumn (pData->hwndListView, 4) ;
	ListView_DeleteColumn (pData->hwndListView, 5) ;

	for( i=0 ; i<pInfo->nThreads ; i++ )
	  {
	    TCHAR szUserTime[32], szKernelTime[32], szThreadId[16] ;
	    LVITEM lvi ;
	    
	    wsprintf (szThreadId, TEXT("%u"), pInfo->aThreads[i].nThreadId) ;

	    if( pInfo->aThreads[i].nUserTime != 0 ) 
	      {
		ULONGLONG nMillis = pInfo->aThreads[i].nUserTime / 10000LL ;
		ULONGLONG nSeconds = nMillis / 1000ULL ;
		ULONGLONG nMinutes = nSeconds / 60ULL ;
		ULONGLONG nHours = nMinutes / 60ULL ;
		wsprintf (szUserTime, TEXT("%02d:%02d:%02d.%03d"), 
			  (int)nHours, (int)(nMinutes%60ULL), (int)(nSeconds%60ULL), (int)(nMillis%1000ULL)) ;
	      }
	    else
	      {
		szUserTime[0] = 0 ;
	      }

	    if( pInfo->aThreads[i].nKernelTime != 0 ) 
	      {
		ULONGLONG nMillis = pInfo->aThreads[i].nKernelTime / 10000LL ;
		ULONGLONG nSeconds = nMillis / 1000ULL ;
		ULONGLONG nMinutes = nSeconds / 60ULL ;
		ULONGLONG nHours = nMinutes / 60ULL ;
		wsprintf (szKernelTime, TEXT("%02d:%02d:%02d.%03d"), 
			  (int)nHours, (int)(nMinutes%60ULL), (int)(nSeconds%60ULL), (int)(nMillis%1000ULL)) ;
	      }
	    else
	      {
		szKernelTime[0] = 0 ;
	      }
      
	    lvi.mask = LVIF_TEXT ;
	    lvi.iSubItem = 0 ;  
	    lvi.iItem = i ;
	    lvi.pszText = szThreadId ;	    
	    ListView_InsertItem (pData->hwndListView, &lvi) ;

	    lvi.mask = LVIF_TEXT ;
	    lvi.iSubItem = 1 ;  
	    lvi.iItem = i ;
	    lvi.pszText = szKernelTime ;	    
	    ListView_SetItem (pData->hwndListView, &lvi) ;

	    lvi.mask = LVIF_TEXT ;
	    lvi.iSubItem = 2 ;  
	    lvi.iItem = i ;
	    lvi.pszText = szUserTime ;	    
	    ListView_SetItem (pData->hwndListView, &lvi) ;
	  }

	free (pInfo) ;
      }
      return 0 ;

    case WM_REFRESH_OBJECTS_INFO:
      {
	PROCINFOOBJECTS *pInfo ;
	LVCOLUMN lvc ;
	UINT nMaxSize = 1024*1024 ;

	int i ;
	
	ListView_DeleteAllItems (pData->hwndListView) ;

	if( pData->nProcAddress == 0 ) return 0 ;

	pInfo = (PROCINFOOBJECTS*) malloc(nMaxSize) ;

	if( ! SpySrv_GetProcInfoObjects (pData->nProcAddress, pInfo, nMaxSize) )
	  {
	    TRACE_ERROR (TEXT("SpySrv_GetProcInfoObjects failed\n")) ;
	    free (pInfo) ;
	    return 0 ;
	  }
	
	lvc.mask = LVCF_WIDTH|LVCF_TEXT ;
	lvc.cx = 50 ;
	lvc.iSubItem = 0 ;
	lvc.pszText = (LPTSTR)g_szType ;
	ListView_InsertColumn (pData->hwndListView, 0, &lvc) ;

	lvc.mask = LVCF_WIDTH|LVCF_TEXT ;
	lvc.cx = 300 ;
	lvc.iSubItem = 1 ;
	lvc.pszText = (LPTSTR)g_szName ;
	ListView_InsertColumn (pData->hwndListView, 1, &lvc) ;

	ListView_DeleteColumn (pData->hwndListView, 2) ;
	ListView_DeleteColumn (pData->hwndListView, 3) ;

	for( i=0 ; i<pInfo->nObjects ; i++ )
	  {	   
	    LVITEM lvi ;	    
	 
	    lvi.mask = LVIF_TEXT ;
	    lvi.iSubItem = 0 ;  
	    lvi.iItem = i ;
	    lvi.pszText = pInfo->aObjects[i].szType ;
	    ListView_InsertItem (pData->hwndListView, &lvi) ;

	    lvi.mask = LVIF_TEXT ;
	    lvi.iSubItem = 1 ;  
	    lvi.iItem = i ;
	    lvi.pszText = pInfo->aObjects[i].szName ; 	    
	    ListView_SetItem (pData->hwndListView, &lvi) ;
	  }

	free (pInfo) ;
      }
      return 0 ;
    }

  return DefWindowProc (hwnd, message, wParam, lParam) ;
}
